﻿using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ecr.AlbumFolderIcon.Core
{
    #region IconExException

    /// <summary>
    /// Exception thrown for unreadable icons
    /// </summary>
    public class IconExException : Exception
    {
        public IconExException()
            : base()
        {
        }

        public IconExException(string message)
            : base(message)
        {
        }

        public IconExException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    #endregion IconExException

    #region IconEx

    /// <summary>
    /// Manages a true Windows Icon with multiple images
    /// (colour depths and sizes)
    /// </summary>
    public class IconEx : IDisposable
    {
        #region Unmanaged Code

        [DllImport("kernel32", CharSet = CharSet.Auto)]
        private extern static IntPtr LoadLibraryEx(
            [MarshalAs(UnmanagedType.LPTStr)]
			string lpLibFileName,
            IntPtr hFile,
            int dwFlags);

        [DllImport("kernel32")]
        private extern static int FreeLibrary(
            IntPtr hLibModule);

        [DllImport("kernel32")]
        private extern static IntPtr LoadResource(
            IntPtr hInstance,
            IntPtr hResInfo);

        [DllImport("kernel32")]
        private extern static IntPtr LockResource(
            IntPtr hResData);

        [DllImport("kernel32", CharSet = CharSet.Auto)]
        private extern static IntPtr FindResource(
            IntPtr hInstance,
            [MarshalAs(UnmanagedType.LPTStr)]
			string lpName,
            IntPtr lpType);

        [DllImport("kernel32")]
        private extern static int SizeofResource(
            IntPtr hInstance,
            IntPtr hResInfo);

        [DllImport("kernel32")]
        private extern static int FreeResource(
            IntPtr hResData);

        #endregion Unmanaged Code

        #region Constants

        private const Int16 IMAGE_ICON = 1;
        private const int LOAD_LIBRARY_AS_DATAFILE = 0x2;
        private const int RT_CURSOR = 1;
        private const int RT_BITMAP = 2;
        private const int RT_ICON = 3;
        private const int DIFFERENCE = 11;
        private const int RT_GROUP_CURSOR = RT_CURSOR + DIFFERENCE;
        private const int RT_GROUP_ICON = RT_ICON + DIFFERENCE;

        #endregion Constants

        #region Structures

        private struct ICONDIRENTRY
        {
            public byte width; // Width of the image
            public byte height; // Height of the image (times 2)
            public byte colorCount; // Number of colors in image (0 if more than 8bpp)
            public byte reserved; // Reserved
            public Int16 wPlanes; // Color Planes
            public Int16 wBitCount; // Bits per pixel
            public int dwBytesInRes; // how many bytes in this resource?
            public int dwImageOffset;// where in the file is this image

            public ICONDIRENTRY(
                BinaryReader br)
            {
                this.width = br.ReadByte();
                this.height = br.ReadByte();
                this.colorCount = br.ReadByte();
                this.reserved = br.ReadByte();
                this.wPlanes = br.ReadInt16();
                this.wBitCount = br.ReadInt16();
                this.dwBytesInRes = br.ReadInt32();
                this.dwImageOffset = br.ReadInt32();
            }

            public void Write(
                BinaryWriter br)
            {
                br.Write(this.width);
                br.Write(this.height);
                br.Write(this.colorCount);
                br.Write(this.reserved);
                br.Write(this.wPlanes);
                br.Write(this.wBitCount);
                br.Write(this.dwBytesInRes);
                br.Write(this.dwImageOffset);
            }

            public override string ToString()
            {
                return string.Format(
                    "Size: ({0},{1}), ColorCount: {2}, Planes: {3}, BitCount {4}, BytesInRes: {5}, ImageOffset {6}",
                    this.width, this.height, this.colorCount,
                    this.wPlanes, this.wBitCount,
                    this.dwBytesInRes, this.dwImageOffset);
            }
        }

        private struct MEMICONDIRENTRY
        {
            public byte width; // Width of the image
            public byte height; // Height of the image (times 2)
            public byte colorCount; // Number of colors in image (0 if more than 8bpp)
            public byte reserved; // Reserved
            public Int16 wPlanes; // Color Planes
            public Int16 wBitCount; // Bits per pixel
            public int dwBytesInRes; // how many bytes in this resource?
            public Int16 nID;// resource id of the image

            public MEMICONDIRENTRY(
                IntPtr lPtr,
                int ofs)
            {
                this.width = Marshal.ReadByte(lPtr, ofs);
                this.height = Marshal.ReadByte(lPtr, ofs + 1);
                this.colorCount = Marshal.ReadByte(lPtr, ofs + 2);
                this.reserved = Marshal.ReadByte(lPtr, ofs + 3);
                this.wPlanes = Marshal.ReadInt16(lPtr, ofs + 4);
                this.wBitCount = Marshal.ReadInt16(lPtr, ofs + 6);
                this.dwBytesInRes = Marshal.ReadInt32(lPtr, ofs + 8);
                this.nID = Marshal.ReadInt16(lPtr, ofs + 12);
            }

            public override string ToString()
            {
                return string.Format(
                    "Size: ({0},{1}), ColorCount: {2}, Planes: {3}, BitCount {4}, BytesInRes: {5}, IconResourceID {6}",
                    this.width, this.height, this.colorCount,
                    this.wPlanes, this.wBitCount,
                    this.dwBytesInRes, this.nID);
            }
        }

        #endregion Structures

        #region Member Variables

        private IconDeviceImageCollection iconCollection = null;
        private string iconFile = null;
        private string libraryFile = null;
        private int resourceId = -1;
        private string resourceName = null;

        #endregion Member Variables

        #region Properties

        /// <summary>
        /// Returns the collection of device images
        /// within this icon
        /// </summary>
        public IconDeviceImageCollection Items
        {
            get
            {
                return this.iconCollection;
            }
            set
            {
            }
        }

        /// <summary>
        /// Gets the file the icon was loaded from
        /// </summary>
        public string IconFile
        {
            get
            {
                return this.iconFile;
            }
        }

        /// <summary>
        /// Gets the library this icon was loaded from
        /// or a blank string if the icon was not sourced
        /// from a library.
        /// </summary>
        public string LibraryFile
        {
            get
            {
                return this.libraryFile;
            }
        }

        /// <summary>
        /// Gets the integer resource id of this icon if it
        /// was loaded from a library
        /// </summary>
        public int ResourceId
        {
            get
            {
                return this.resourceId;
            }
        }

        /// <summary>
        /// Gets the string resource id of this icon if it
        /// was loaded from a library
        /// </summary>
        public string ResourceName
        {
            get
            {
                return this.resourceName;
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Loads an icon from the specified file
        /// </summary>
        /// <param name="iconFile">File containing icon</param>
        public void FromFile(
            string iconFile)
        {
            loadFromFile(iconFile);
        }

        /// <summary>
        /// Loads an icon from an executable or library
        /// with the specified integer resource id
        /// </summary>
        /// <param name="libraryFile">Executable or DLL
        /// containing icon</param>
        /// <param name="resourceId">Integer resource identifier</param>
        public void FromLibrary(
            string libraryFile,
            int resourceId)
        {
            loadInitialise();
            string resourceName = String.Format("#{0:N0}", resourceId);
            loadFromLibrary(libraryFile, resourceName);
        }

        /// <summary>
        /// Loads an icon from an executable or library
        /// with the specified string resource id
        /// </summary>
        /// <param name="libraryFile">Executable or DLL
        /// containing icon</param>
        /// <param name="resourceName">String resource identifier</param>
        public void FromLibrary(
            string libraryFile,
            string resourceName
            )
        {
            loadInitialise();
            loadFromLibrary(libraryFile, resourceName);
        }

        /// <summary>
        /// Saves the icon to the specified file
        /// </summary>
        /// <param name="iconFile">File to save to</param>
        public void Save(
            string iconFile
            )
        {
            // open the file for writing, truncate if exists
            FileStream fs = new FileStream(
                iconFile,
                FileMode.Create,
                FileAccess.Write,
                FileShare.Read);
            BinaryWriter bw = null;
            try
            {
                bw = new BinaryWriter(fs);

                // write out the icon header:
                writeIconFileHeader(bw);

                // write out the icon directory entries:
                int iconOffset = 6 + 16 * this.iconCollection.Count;
                foreach (IconDeviceImage idi in this.iconCollection)
                {
                    int bytesInRes = idi.IconImageDataBytes();

                    ICONDIRENTRY ide = new ICONDIRENTRY();
                    ide.width = (byte)idi.IconSize.Width;
                    ide.height = (byte)idi.IconSize.Height;
                    switch (idi.ColorDepth)
                    {
                        case ColorDepth.Depth4Bit:
                            ide.colorCount = 16;
                            ide.wBitCount = 4;
                            break;

                        case ColorDepth.Depth8Bit:
                            ide.colorCount = 255;
                            ide.wBitCount = 8;
                            break;

                        case ColorDepth.Depth16Bit:
                            ide.colorCount = 0;
                            ide.wBitCount = 16;
                            break;

                        case ColorDepth.Depth24Bit:
                            ide.colorCount = 0;
                            ide.wBitCount = 24;
                            break;

                        case ColorDepth.Depth32Bit:
                            ide.colorCount = 0;
                            ide.wBitCount = 32;
                            break;
                    }
                    ide.wPlanes = 1;
                    ide.dwBytesInRes = bytesInRes;
                    ide.dwImageOffset = iconOffset;
                    ide.Write(bw);

                    iconOffset += bytesInRes;
                }

                // write out the icon data:
                foreach (IconDeviceImage idi in this.iconCollection)
                {
                    idi.SaveIconBitmapData(bw);
                }
            }
            catch (Exception ex)
            {
                if (ex is SystemException)
                {
                    throw ex;
                }
                else
                {
                    throw new IconExException(ex.Message, ex);
                }
            }
            finally
            {
                if (bw != null)
                {
                    bw.Close();
                }
            }
        }

        #endregion Methods

        #region Private Implementation

        private void loadInitialise()
        {
            this.iconFile = "";
            this.resourceId = -1;
            this.libraryFile = "";
            this.iconCollection = new IconDeviceImageCollection();
        }

        // this method is too long, I'm sorry...
        private void loadFromLibrary(
            string libraryFile,
            string resourceName
            )
        {
            string msg = "";
            bool failed = false;
            IntPtr hGlobal = IntPtr.Zero;
            IntPtr hRsrc = IntPtr.Zero;
            IntPtr hLibrary = IntPtr.Zero;
            IntPtr lPtr = IntPtr.Zero;

            try
            {
                hLibrary = LoadLibraryEx(
                    libraryFile,
                    IntPtr.Zero,
                    LOAD_LIBRARY_AS_DATAFILE);
                if (hLibrary != IntPtr.Zero)
                {
                    hRsrc = FindResource(
                        hLibrary,
                        resourceName,
                        (IntPtr)RT_GROUP_ICON);
                    if (hRsrc != IntPtr.Zero)
                    {
                        hGlobal = LoadResource(hLibrary, hRsrc);
                        if (hGlobal != IntPtr.Zero)
                        {
                            lPtr = LockResource(hGlobal);
                            if (lPtr != IntPtr.Zero)
                            {
                                // now we can read the header:
                                int iconCount = readResourceIconFileHeader(lPtr);
                                // read the directory:
                                MEMICONDIRENTRY[] ide = new MEMICONDIRENTRY[iconCount];
                                int ofs = 6;
                                for (int iconEntry = 0; iconEntry < iconCount; iconEntry++)
                                {
                                    ide[iconEntry] = new MEMICONDIRENTRY(lPtr, ofs);
                                    //Console.WriteLine(ide[iconEntry].ToString());
                                    ofs += 14;
                                }
                                FreeResource(hGlobal);
                                hGlobal = IntPtr.Zero;

                                // we have the directory, so now can load the icons:
                                IconDeviceImage[] icons = new IconDeviceImage[iconCount];
                                // read the icons:
                                for (int iconEntry = 0; iconEntry < iconCount; iconEntry++)
                                {
                                    // find the specified icon:
                                    string resName = String.Format("#{0:N0}", ide[iconEntry].nID);
                                    hRsrc = FindResource(
                                        hLibrary,
                                        resName,
                                        (IntPtr)RT_ICON);
                                    if (hRsrc == IntPtr.Zero)
                                    {
                                        msg = String.Format(
                                            "Could not find the component icon resource with id {0}",
                                            ide[iconEntry].nID);
                                        failed = true;
                                        break;
                                    }
                                    else
                                    {
                                        // load the resource:
                                        hGlobal = LoadResource(
                                            hLibrary,
                                            hRsrc);
                                        if (hGlobal == IntPtr.Zero)
                                        {
                                            msg = String.Format(
                                                "Could not load the component icon resource with id {0}",
                                                ide[iconEntry].nID);
                                            failed = true;
                                            break;
                                        }
                                        else
                                        {
                                            // check the size:
                                            int resSize = SizeofResource(hLibrary, hRsrc);
                                            if ((resSize > 0) && (resSize == ide[iconEntry].dwBytesInRes))
                                            {
                                                // ok
                                                lPtr = LockResource(hGlobal);
                                                byte[] b = new byte[resSize];
                                                Marshal.Copy(lPtr, b, 0, resSize);
                                                icons[iconEntry] = new IconDeviceImage(b);
                                            }
                                            else
                                            {
                                                msg = String.Format(
                                                    "Component icon resource with id {0} is corrupt",
                                                    ide[iconEntry].nID);
                                                failed = true;
                                            }
                                        }
                                    }
                                }
                                if (!failed)
                                {
                                    // Add the icons to the collection:
                                    this.iconCollection = new IconDeviceImageCollection(icons);
                                }
                            }
                            else
                            {
                                msg = "Can't lock resource for reading.";
                                failed = true;
                            }
                        }
                        else
                        {
                            msg = "Can't load resource for reading.";
                            failed = true;
                        }
                    }
                    else
                    {
                        msg = "Can't find resource.";
                        failed = true;
                    }
                }
                else
                {
                    msg = "Can't load library.";
                    failed = true;
                }
            }
            catch (Exception ex)
            {
                failed = true;
                msg = ex.Message;
            }
            finally
            {
                // clear up handles:
                if (hGlobal != IntPtr.Zero)
                {
                    FreeResource(hGlobal);
                }
                if (hLibrary != IntPtr.Zero)
                {
                    FreeLibrary(hLibrary);
                }
                if (failed)
                {
                    throw new IconExException(msg);
                }
            }
        }

        private void loadFromFile(string iconFile)
        {
            loadInitialise();

            // Open the file
            FileStream fs = new FileStream(
                iconFile,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read);
            BinaryReader br = new BinaryReader(fs);

            try
            {
                // read the header:
                int iconCount = readIconFileHeader(br);
                // read the directory:
                ICONDIRENTRY[] ide = new ICONDIRENTRY[iconCount];
                for (int iconEntry = 0; iconEntry < iconCount; iconEntry++)
                {
                    ide[iconEntry] = new ICONDIRENTRY(br);
                }
                IconDeviceImage[] icons = new IconDeviceImage[iconCount];
                // read the actual icons:
                for (int iconEntry = 0; iconEntry < iconCount; iconEntry++)
                {
                    fs.Seek(ide[iconEntry].dwImageOffset, SeekOrigin.Begin);
                    byte[] b = new byte[ide[iconEntry].dwBytesInRes];
                    br.Read(b, 0, ide[iconEntry].dwBytesInRes);
                    icons[iconEntry] = new IconDeviceImage(b);
                }
                // Add the icons to the collection:
                this.iconCollection = new IconDeviceImageCollection(icons);
            }
            catch (Exception ex)
            {
                if (ex is SystemException)
                {
                    throw ex;
                }
                else
                {
                    throw new IconExException("Failed to read icon file.", ex);
                }
            }
            finally
            {
                br.Close();
            }

            this.iconFile = iconFile;
        }

        private int readResourceIconFileHeader(
            IntPtr lPtr)
        {
            int idReserved = Marshal.ReadInt16(lPtr);
            int idType = Marshal.ReadInt16(lPtr, 2);
            int idCount = Marshal.ReadInt16(lPtr, 4);
            if ((idReserved == 0) &&
                (idType == IMAGE_ICON) &&
                (idCount > 0) &&
                (idCount < 1024))
            {
                return idCount;
            }
            else
            {
                throw new IconExException("Invalid Icon File Header");
            }
        }

        private int readIconFileHeader(
            BinaryReader br)
        {
            int idReserved = br.ReadInt16();
            int idType = br.ReadInt16();
            int idCount = br.ReadInt16();
            if ((idReserved == 0) &&
                (idType == IMAGE_ICON) &&
                (idCount > 0) &&
                (idCount < 1024))
            {
                return idCount;
            }
            else
            {
                throw new IconExException("Invalid Icon File Header");
            }
        }

        private void writeIconFileHeader(
            BinaryWriter bw)
        {
            Int16 idReserved = 0;
            bw.Write(idReserved);
            Int16 idType = IMAGE_ICON;
            bw.Write(idType);
            Int16 idCount = (Int16)this.Items.Count;
            bw.Write(idCount);
        }

        #endregion Private Implementation

        #region Constructor, Dispose

        /// <summary>
        /// Constructs a new, empty instance of the IconEx
        /// object
        /// </summary>
        public IconEx()
        {
        }

        public IconEx(Icon icon)
        {
            IconDeviceImage[] icons = new IconDeviceImage[1];
            icons[0] = new IconDeviceImage(icon);
            this.iconCollection = new IconDeviceImageCollection(icons);
            this.iconFile = "";
        }

        /// <summary>
        /// Constructs an IconEx instance and opens the icon
        /// file specified.
        /// </summary>
        /// <param name="iconFile">Icon file to read</param>
        public IconEx(string iconFile)
        {
            loadFromFile(iconFile);
        }

        /// <summary>
        /// Constructs an IconEx instance and opens the icon
        /// from the specified library (Executable or DLL)
        /// with the specified integer resource identifier
        /// </summary>
        /// <param name="libraryFile">Executable or DLL to extract
        /// icon from</param>
        /// <param name="resourceId">Integer resource Id</param>
        public IconEx(string libraryFile, int resourceId)
        {
            FromLibrary(libraryFile, resourceId);
        }

        /// <summary>
        /// Constructs an IconEx instance and opens the icon
        /// from the specified library (Executable or DLL)
        /// with the specified string resource identifier
        /// </summary>
        /// <param name="libraryFile">Executable or DLL to extract
        /// icon from</param>
        /// <param name="resourceName">String resource Id</param>
        public IconEx(
            string libraryFile,
            string resourceName
            )
        {
            FromLibrary(libraryFile, resourceName);
        }

        public void Dispose()
        {
            if (this.iconCollection != null)
            {
                iconCollection.Dispose();
                iconCollection = null;
            }
        }

        #endregion Constructor, Dispose
    }

    #endregion IconEx

    #region IconDeviceImageCollection

    /// <summary>
    /// Manages a read/write collection of icon resources
    /// within an Icon file
    /// </summary>
    public class IconDeviceImageCollection : CollectionBase, IDisposable
    {
        #region Member Variables

        #endregion Member Variables

        #region Methods

        /// <summary>
        /// Add a new icon device image
        /// </summary>
        /// <param name="icon">Icon to add</param>
        public void Add(IconDeviceImage icon)
        {
            foreach (IconDeviceImage iconExisting in this.InnerList)
            {
                if (icon.IconSize.Equals(iconExisting.IconSize) &&
                    icon.ColorDepth.Equals(iconExisting.ColorDepth))
                {
                    throw new IconExException("An Icon Device Image with the same size and colour depth already exists in this icon");
                }
            }
            this.InnerList.Add(icon);
        }

        /// <summary>
        /// Gets the IconDevice Image at the specified
        /// index
        /// </summary>
        public IconDeviceImage this[int index]
        {
            get
            {
                return (IconDeviceImage)this.InnerList[index];
            }
        }

        #endregion Methods

        #region Constructor, Dispose

        /// <summary>
        ///  Constructs a new, empty collection of device
        ///  images.
        /// </summary>
        public IconDeviceImageCollection()
        {
        }

        /// <summary>
        /// Constructs a new collection of device images
        /// </summary>
        /// <param name="icons">Icons to add</param>
        public IconDeviceImageCollection(
            IconDeviceImage[] icons
            )
        {
            foreach (IconDeviceImage icon in icons)
            {
                this.InnerList.Add(icon);
            }
        }

        public void Dispose()
        {
            if (this.InnerList != null)
            {
                foreach (IconDeviceImage icon in this.InnerList)
                {
                    icon.Dispose();
                }
                this.InnerList.Clear();
            }
        }

        #endregion Constructor, Dispose
    }

    #endregion IconDeviceImageCollection

    #region IconDeviceImage

    /// <summary>
    /// Manages a single icon device image within an
    /// Icon file
    /// </summary>
    public class IconDeviceImage : IDisposable
    {
        #region Member Variables

        private Size size;
        private System.Windows.Forms.ColorDepth colorDepth = ColorDepth.Depth4Bit;
        private byte[] data;
        private IntPtr hIcon = IntPtr.Zero;

        #endregion Member Variables

        #region Unmanaged Code

        [DllImport("gdi32")]
        private static extern int SetDIBitsToDevice(
            IntPtr hdc,
            int X, int Y, int dx, int dy,
            int SrcX, int SrcY, int Scan, int NumScans,
            IntPtr Bits,
            IntPtr BitsInfo,
            int wUsage);

        [DllImport("gdi32")]
        public static extern int GetDIBits(
            IntPtr hdc,
            IntPtr hBitmap,
            int nStartScan,
            int nNumScans,
            IntPtr Bits,
            IntPtr BitsInfo,
            int wUsage);

        private const int DIB_RGB_COLORS = 0; //  color table in RGBs
        private const int DIB_PAL_COLORS = 1; //  color table in palette indices
        private const int DIB_PAL_INDICES = 2; //  No color table indices into surf palette
        private const int DIB_PAL_PHYSINDICES = 2; //  No color table indices into surf palette
        private const int DIB_PAL_LOGINDICES = 4; //  No color table indices into DC palette

        // Bitmap compression types:
        private const int BI_RGB = 0x0;

        private const int BI_RLE4 = 0x2;
        private const int BI_RLE8 = 0x1;

        [DllImport("gdi32")]
        private static extern IntPtr CreateCompatibleDC(
            IntPtr hdc);

        [DllImport("gdi32", CharSet = CharSet.Auto)]
        private static extern IntPtr CreateDC(
            [MarshalAs(UnmanagedType.LPTStr)]
			string lpDriverName,
            IntPtr lpDeviceName,
            IntPtr lpOutput,
            IntPtr lpInitData);

        [DllImport("gdi32")]
        private static extern IntPtr CreateCompatibleBitmap(
            IntPtr hdc,
            int width,
            int height);

        [DllImport("gdi32")]
        private static extern IntPtr SelectObject(
            IntPtr hdc, IntPtr hObject);

        [DllImport("gdi32")]
        private static extern int DeleteObject(
            IntPtr hObject);

        [DllImport("gdi32")]
        private static extern int DeleteDC(
            IntPtr hdc);

        [DllImport("user32")]
        private static extern int DestroyIcon(
            IntPtr hIcon);

        [DllImport("user32")]
        private static extern IntPtr CreateIconIndirect(
            ref ICONINFO piconInfo);

        private const Int16 IMAGE_ICON = 1;

        #endregion Unmanaged Code

        #region Structures

        #region ICONINFO.  Used by the CreateIconIndirect API function

        private struct ICONINFO
        {
            public int fIcon;
            public int xHotspot;
            public int yHotspot;
            public IntPtr hBmMask;
            public IntPtr hBmColor;
        }

        #endregion ICONINFO.  Used by the CreateIconIndirect API function

        #region BITMAPINFOHEADER.  This is stored at the start of an icon's data

        private struct BITMAPINFOHEADER
        {
            public int biSize;
            public int biWidth;
            public int biHeight;
            public Int16 biPlanes;
            public Int16 biBitCount;
            public int biCompression;
            public int biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public int biClrUsed;
            public int biClrImportant;

            public BITMAPINFOHEADER(
                Size size,
                ColorDepth colorDepth
                )
            {
                this.biSize = 0;
                this.biWidth = size.Width;
                this.biHeight = size.Height * 2;
                this.biPlanes = 1;
                this.biCompression = BI_RGB;
                this.biSizeImage = 0;
                this.biXPelsPerMeter = 0;
                this.biYPelsPerMeter = 0;
                this.biClrUsed = 0;
                this.biClrImportant = 0;
                switch (colorDepth)
                {
                    case ColorDepth.Depth4Bit:
                        this.biBitCount = 4;
                        break;

                    case ColorDepth.Depth8Bit:
                        this.biBitCount = 8;
                        break;

                    case ColorDepth.Depth16Bit:
                        this.biBitCount = 16;
                        break;

                    case ColorDepth.Depth24Bit:
                        this.biBitCount = 24;
                        break;

                    case ColorDepth.Depth32Bit:
                        this.biBitCount = 32;
                        break;

                    default:
                        this.biBitCount = 4;
                        break;
                }
                this.biSize = Marshal.SizeOf(this.GetType());
            }

            public void Write(
                BinaryWriter bw)
            {
                bw.Write(this.biSize);
                bw.Write(this.biWidth);
                bw.Write(this.biHeight);
                bw.Write(this.biPlanes);
                bw.Write(this.biBitCount);
                bw.Write(this.biCompression);
                bw.Write(this.biSizeImage);
                bw.Write(this.biXPelsPerMeter);
                bw.Write(this.biYPelsPerMeter);
                bw.Write(this.biClrUsed);
                bw.Write(this.biClrImportant);
            }

            public BITMAPINFOHEADER(byte[] data)
            {
                MemoryStream ms = new MemoryStream(data, false);
                BinaryReader br = new BinaryReader(ms);
                biSize = br.ReadInt32();
                biWidth = br.ReadInt32();
                biHeight = br.ReadInt32();
                biPlanes = br.ReadInt16();
                biBitCount = br.ReadInt16();
                biCompression = br.ReadInt32();
                biSizeImage = br.ReadInt32();
                biXPelsPerMeter = br.ReadInt32();
                biYPelsPerMeter = br.ReadInt32();
                biClrUsed = br.ReadInt32();
                biClrImportant = br.ReadInt32();
                br.Close();
            }

            public override string ToString()
            {
                return string.Format(
                    "biSize: {0}, biWidth: {1}, biHeight: {2}, biPlanes: {3}, biBitCount: {4}, biCompression: {5}, biSizeImage: {6}, biXPelsPerMeter: {7}, biYPelsPerMeter {8}, biClrUsed {9}, biClrImportant {10}",
                    biSize, biWidth, biHeight, biPlanes, biBitCount,
                    biCompression, biSizeImage, biXPelsPerMeter, biYPelsPerMeter,
                    biClrUsed, biClrImportant);
            }
        }

        #endregion BITMAPINFOHEADER.  This is stored at the start of an icon's data

        #region RQBQUAD. Used to store colours in a paletised icon (2, 4 or 8 bit)

        private struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;

            public RGBQUAD(
                byte r, byte g, byte b, byte alpha
                )
            {
                rgbBlue = b;
                rgbGreen = g;
                rgbRed = r;
                rgbReserved = 0; //alpha;
            }

            public RGBQUAD(
                Color c
                )
            {
                rgbBlue = c.B;
                rgbGreen = c.G;
                rgbRed = c.R;
                rgbReserved = 0; //c.A;
            }

            public void Write(
                BinaryWriter bw)
            {
                bw.Write(this.rgbBlue);
                bw.Write(this.rgbGreen);
                bw.Write(this.rgbRed);
                bw.Write(this.rgbReserved);
            }

            public override string ToString()
            {
                return string.Format(
                    "rgbBlue: {0}, rgbGreen: {1}, rgbRed: {2}",
                    rgbBlue, rgbGreen, rgbRed);
            }
        }

        #endregion RQBQUAD. Used to store colours in a paletised icon (2, 4 or 8 bit)

        #endregion Structures

        #region Properties

        /// <summary>
        /// Gets the Icon handle for this device image
        /// </summary>
        public IntPtr Handle
        {
            get
            {
                return this.hIcon;
            }
        }

        /// <summary>
        /// Gets the size of this device image
        /// </summary>
        public Size IconSize
        {
            get
            {
                return this.size;
            }
        }

        /// <summary>
        /// Gets the colour depth of this device image
        /// </summary>
        public System.Windows.Forms.ColorDepth ColorDepth
        {
            get
            {
                return this.colorDepth;
            }
        }

        /// <summary>
        /// Gets/sets the Mask Image of the icon as a bitmap
        /// </summary>
        public System.Drawing.Bitmap MaskImage
        {
            get
            {
                IntPtr junk = IntPtr.Zero;
                Bitmap bm = getIconBitmap(true, false, ref junk);
                return bm;
            }
            set
            {
                setMaskBitsFromBitmap(value);
            }
        }

        /// <summary>
        /// Gets/sets the image portion of the icon as a bitmap
        /// </summary>
        public System.Drawing.Bitmap IconImage
        {
            get
            {
                IntPtr junk = IntPtr.Zero;
                Bitmap bm = getIconBitmap(false, false, ref junk);
                return bm;
            }
            set
            {
                setImageBitsFromBitmap(value);
            }
        }

        /// <summary>
        /// Gets the device image as a managed icon
        /// Note that you should clone the icon if you want to keep it
        /// after this class has been disposed.
        /// </summary>
        public System.Drawing.Icon Icon
        {
            get
            {
                System.Drawing.Icon icon = System.Drawing.Icon.FromHandle(this.hIcon);
                return icon;
            }
        }

        #endregion Properties

        #region Private Implementation

        private void setMaskBitsFromBitmap(
            Bitmap bm
            )
        {
            IntPtr hdcc = CreateDC("DISPLAY", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
            IntPtr hdc = CreateCompatibleDC(hdcc);
            DeleteDC(hdcc);
            IntPtr hBmp = bm.GetHbitmap();

            BITMAPINFOHEADER bmInfoHdr = new BITMAPINFOHEADER(
                this.size, this.colorDepth);

            // Now prepare the for GetDIBits call:
            RGBQUAD rgbQuad = new RGBQUAD();
            int monoBmHdrSize = bmInfoHdr.biSize + Marshal.SizeOf(rgbQuad) * 2;

            IntPtr bitsInfo = Marshal.AllocCoTaskMem(
                monoBmHdrSize);
            Marshal.WriteInt32(bitsInfo, Marshal.SizeOf(bmInfoHdr));
            Marshal.WriteInt32(bitsInfo, 4, this.size.Width);
            Marshal.WriteInt32(bitsInfo, 8, this.size.Height);
            Marshal.WriteInt16(bitsInfo, 12, 1);
            Marshal.WriteInt16(bitsInfo, 14, 1);
            Marshal.WriteInt32(bitsInfo, 16, BI_RGB);
            Marshal.WriteInt32(bitsInfo, 20, 0);
            Marshal.WriteInt32(bitsInfo, 24, 0);
            Marshal.WriteInt32(bitsInfo, 28, 0);
            Marshal.WriteInt32(bitsInfo, 32, 0);
            Marshal.WriteInt32(bitsInfo, 36, 0);
            // Write the black and white colour indices:
            Marshal.WriteInt32(bitsInfo, 40, 0);
            Marshal.WriteByte(bitsInfo, 44, 255);
            Marshal.WriteByte(bitsInfo, 45, 255);
            Marshal.WriteByte(bitsInfo, 46, 255);
            Marshal.WriteByte(bitsInfo, 47, 0);

            int maskImageBytes = MaskImageSize(bmInfoHdr);
            IntPtr bits = Marshal.AllocCoTaskMem(maskImageBytes);

            int success = GetDIBits(hdc, hBmp, 0, this.size.Height, bits, bitsInfo, DIB_RGB_COLORS);

            Marshal.Copy(bits, data, MaskImageIndex(bmInfoHdr), maskImageBytes);

            // Free memory:
            Marshal.FreeCoTaskMem(bits);
            Marshal.FreeCoTaskMem(bitsInfo);

            DeleteObject(hBmp);
            DeleteDC(hdc);

            createIcon();
        }

        private void setImageBitsFromBitmap(
            Bitmap bm
            )
        {
            IntPtr hdcc = CreateDC("DISPLAY", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
            IntPtr hdc = CreateCompatibleDC(hdcc);
            DeleteDC(hdcc);
            IntPtr hBmp = bm.GetHbitmap();

            BITMAPINFOHEADER bmInfoHdr = new BITMAPINFOHEADER(
                this.size, this.colorDepth);

            // Now prepare for GetDIBits call:
            int xorIndex = XorImageIndex(bmInfoHdr);
            int xorImageBytes = XorImageSize(bmInfoHdr);

            // Get the BITMAPINFO header into the pointer:
            IntPtr bitsInfo = Marshal.AllocCoTaskMem(
                xorIndex);
            Marshal.Copy(data, 0, bitsInfo, xorIndex);
            // fix the height:
            Marshal.WriteInt32(bitsInfo, 8, bmInfoHdr.biHeight / 2);

            IntPtr bits = Marshal.AllocCoTaskMem(xorImageBytes);

            int success = GetDIBits(hdc, hBmp, 0, this.size.Height, bits, bitsInfo, DIB_RGB_COLORS);

            Marshal.Copy(bits, data, xorIndex, xorImageBytes);

            // Free memory:
            Marshal.FreeCoTaskMem(bits);
            Marshal.FreeCoTaskMem(bitsInfo);

            DeleteObject(hBmp);
            DeleteDC(hdc);

            createIcon();
        }

        private void setDeviceImage(
            Size size,
            System.Windows.Forms.ColorDepth colorDepth
            )
        {
            this.size = size;
            this.colorDepth = colorDepth;
            // Initialise the data:
            BITMAPINFOHEADER bmInfoHdr = new BITMAPINFOHEADER(
                size, colorDepth);
            this.data = new byte[
                this.MaskImageIndex(bmInfoHdr) + this.MaskImageSize(bmInfoHdr)];

            MemoryStream mw = new MemoryStream(this.data, 0, this.data.Length, true);
            BinaryWriter bw = new BinaryWriter(mw);
            bmInfoHdr.Write(bw);
            // Write the colour indexes if required:
            switch (this.colorDepth)
            {
                case ColorDepth.Depth4Bit:
                    write16ColorPalette(bw);
                    break;

                case ColorDepth.Depth8Bit:
                    write256ColorPalette(bw);
                    break;
            }
            bw.Close();
        }

        private void write16ColorPalette(
            BinaryWriter bw)
        {
            // Write out 16 entries containing the
            // standard colour palette:
            writeColor(bw, Color.Black);
            writeColor(bw, Color.White);
            writeColor(bw, Color.Red);
            writeColor(bw, Color.Green);
            writeColor(bw, Color.Blue);
            writeColor(bw, Color.Yellow);
            writeColor(bw, Color.Magenta);
            writeColor(bw, Color.Cyan);
            writeColor(bw, Color.Gray);
            writeColor(bw, Color.DarkRed);
            writeColor(bw, Color.DarkGreen);
            writeColor(bw, Color.DarkBlue);
            writeColor(bw, Color.Olive);
            writeColor(bw, Color.Purple);
            writeColor(bw, Color.Teal);
            writeColor(bw, Color.DarkGray);
        }

        private void write256ColorPalette(
            BinaryWriter bw)
        {
            KnownColor kc = KnownColor.ActiveBorder;
            Array colors = Enum.GetValues(kc.GetType());
            int i = 0;
            foreach (KnownColor color in colors)
            {
                writeColor(bw, Color.FromKnownColor(color));
                i++;
                if (i > 255)
                {
                    break;
                }
            }
        }

        private void writeColor(
            BinaryWriter bw,
            Color color
            )
        {
            RGBQUAD r = new RGBQUAD(color);
            r.Write(bw);
        }

        private Bitmap getIconBitmap(
            bool mask,
            bool returnHandle,
            ref IntPtr hBmp
            )
        {
            // Bitmap to return
            Bitmap bm = null;

            // Get bitmap info:
            BITMAPINFOHEADER bmInfoHdr = new BITMAPINFOHEADER(data);

            if (mask)
            {
                // extract monochrome mask
                IntPtr hdc = CreateCompatibleDC(IntPtr.Zero);
                hBmp = CreateCompatibleBitmap(hdc, bmInfoHdr.biWidth, bmInfoHdr.biHeight / 2);
                IntPtr hBmpOld = SelectObject(hdc, hBmp);

                // Prepare BitmapInfoHeader for mono bitmap:
                RGBQUAD rgbQuad = new RGBQUAD();
                int monoBmHdrSize = bmInfoHdr.biSize + Marshal.SizeOf(rgbQuad) * 2;

                IntPtr bitsInfo = Marshal.AllocCoTaskMem(
                    monoBmHdrSize);
                Marshal.WriteInt32(bitsInfo, Marshal.SizeOf(bmInfoHdr));
                Marshal.WriteInt32(bitsInfo, 4, bmInfoHdr.biWidth);
                Marshal.WriteInt32(bitsInfo, 8, bmInfoHdr.biHeight / 2);
                Marshal.WriteInt16(bitsInfo, 12, 1);
                Marshal.WriteInt16(bitsInfo, 14, 1);
                Marshal.WriteInt32(bitsInfo, 16, BI_RGB);
                Marshal.WriteInt32(bitsInfo, 20, 0);
                Marshal.WriteInt32(bitsInfo, 24, 0);
                Marshal.WriteInt32(bitsInfo, 28, 0);
                Marshal.WriteInt32(bitsInfo, 32, 0);
                Marshal.WriteInt32(bitsInfo, 36, 0);
                // Write the black and white colour indices:
                Marshal.WriteInt32(bitsInfo, 40, 0);
                Marshal.WriteByte(bitsInfo, 44, 255);
                Marshal.WriteByte(bitsInfo, 45, 255);
                Marshal.WriteByte(bitsInfo, 46, 255);
                Marshal.WriteByte(bitsInfo, 47, 0);

                // Prepare Mask bits:
                int maskImageBytes = MaskImageSize(bmInfoHdr);
                IntPtr bits = Marshal.AllocCoTaskMem(maskImageBytes);
                Marshal.Copy(data, MaskImageIndex(bmInfoHdr), bits, maskImageBytes);

                int success = SetDIBitsToDevice(
                    hdc,
                    0, 0, bmInfoHdr.biWidth, bmInfoHdr.biHeight / 2,
                    0, 0, 0, bmInfoHdr.biHeight / 2,
                    bits,
                    bitsInfo,
                    DIB_RGB_COLORS);

                Marshal.FreeCoTaskMem(bits);
                Marshal.FreeCoTaskMem(bitsInfo);

                SelectObject(hdc, hBmpOld);
                DeleteObject(hdc);
            }
            else
            {
                // extract colour (XOR) part of image:

                // Create bitmap:
                IntPtr hdcDesktop = CreateDC("DISPLAY", IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                IntPtr hdc = CreateCompatibleDC(hdcDesktop);
                hBmp = CreateCompatibleBitmap(hdcDesktop, bmInfoHdr.biWidth, bmInfoHdr.biHeight / 2);
                DeleteDC(hdcDesktop);
                IntPtr hBmpOld = SelectObject(hdc, hBmp);

                // Find the index of the XOR bytes:
                int xorIndex = XorImageIndex(bmInfoHdr);
                int xorImageSize = XorImageSize(bmInfoHdr);

                // Get Bitmap info header to a pointer:
                IntPtr bitsInfo = Marshal.AllocCoTaskMem(xorIndex);
                Marshal.Copy(data, 0, bitsInfo, xorIndex);
                // fix the height:
                Marshal.WriteInt32(bitsInfo, 8, bmInfoHdr.biHeight / 2);

                // Get the XOR bits:
                IntPtr bits = Marshal.AllocCoTaskMem(xorImageSize);
                Marshal.Copy(data, xorIndex, bits, xorImageSize);

                int success = SetDIBitsToDevice(
                    hdc,
                    0, 0, bmInfoHdr.biWidth, bmInfoHdr.biHeight / 2,
                    0, 0, 0, bmInfoHdr.biHeight / 2,
                    bits,
                    bitsInfo,
                    DIB_RGB_COLORS);

                Marshal.FreeCoTaskMem(bits);
                Marshal.FreeCoTaskMem(bitsInfo);

                SelectObject(hdc, hBmpOld);
                DeleteObject(hdc);
            }

            if (!returnHandle)
            {
                // the bitmap will own the handle and clear
                // it up when it is disposed.  Otherwise
                // need to call DeleteObject on hBmp
                // returned.
                bm = Bitmap.FromHbitmap(hBmp);
            }
            return bm;
        }

        private int MaskImageIndex(
            BITMAPINFOHEADER bmInfoHeader
            )
        {
            int maskImageIndex = XorImageIndex(bmInfoHeader);
            maskImageIndex += XorImageSize(bmInfoHeader);
            return maskImageIndex;
        }

        private int XorImageSize(
            BITMAPINFOHEADER bmInfoHeader
            )
        {
            int imageBytes = (bmInfoHeader.biHeight / 2 *
                WidthBytes(bmInfoHeader.biWidth * bmInfoHeader.biBitCount * bmInfoHeader.biPlanes));
            return imageBytes;
        }

        private int MaskImageSize(
            BITMAPINFOHEADER bmInfoHeader
            )
        {
            int imageBytes = bmInfoHeader.biHeight / 2 *
                WidthBytes(bmInfoHeader.biWidth);
            return imageBytes;
        }

        private int WidthBytes(int width)
        {
            // Returns the width of a row in a DIB Bitmap given the
            // number of bits.  DIB Bitmap rows always align on a
            // DWORD boundary.
            int widthBytes = ((width + 31) / 32) * 4;
            return widthBytes;
        }

        private int XorImageIndex(
            BITMAPINFOHEADER bmInfoHeader)
        {
            // Returns the position of the DIB bitmap bits within a
            // DIB bitmap array:
            RGBQUAD rgbq = new RGBQUAD();
            return Marshal.SizeOf(bmInfoHeader) +
                dibNumColors(bmInfoHeader) * Marshal.SizeOf(rgbq);
        }

        private int dibNumColors(
            BITMAPINFOHEADER bmInfoHeader)
        {
            int colorCount = 0;
            if (bmInfoHeader.biClrUsed != 0)
            {
                colorCount = bmInfoHeader.biClrUsed;
            }
            else
            {
                switch (bmInfoHeader.biBitCount)
                {
                    case 1:
                        colorCount = 2;
                        break;

                    case 4:
                        colorCount = 16;
                        break;

                    case 8:
                        colorCount = 256;
                        break;
                }
            }
            return colorCount;
        }

        /// <summary>
        /// Internal method.  Returns the number of bytes in the
        /// icon data.  Not intended for public use.
        /// </summary>
        /// <returns>Number of bytes of icon data</returns>
        internal int IconImageDataBytes()
        {
            return this.data.Length;
        }

        /// <summary>
        /// Internal method.  Writes the icon bitmap data to
        /// the specified BinaryWriter.  Not intended for
        /// public use.
        /// </summary>
        /// <param name="bw">BinaryWriter to write to</param>
        internal void SaveIconBitmapData(
            BinaryWriter bw)
        {
            bw.Write(this.data, 0, this.data.Length);
        }

        private void createIcon()
        {
            if (this.hIcon != IntPtr.Zero)
            {
                DestroyIcon(this.hIcon);
                this.hIcon = IntPtr.Zero;
            }

            ICONINFO ii = new ICONINFO();
            ii.fIcon = IMAGE_ICON;
            getIconBitmap(false, true, ref ii.hBmColor);
            getIconBitmap(true, true, ref ii.hBmMask);

            this.hIcon = CreateIconIndirect(ref ii);

            DeleteObject(ii.hBmColor);
            DeleteObject(ii.hBmMask);
        }

        #endregion Private Implementation

        #region Constructor, Dispose

        /// <summary>
        /// Constructs a new IconDeviceImage with the specified
        /// size and colour depth.
        /// </summary>
        /// <param name="size">Size of device image</param>
        /// <param name="colorDepth">Colour depth of device image</param>
        public IconDeviceImage(Size size,
            System.Windows.Forms.ColorDepth colorDepth
            )
        {
            setDeviceImage(size, colorDepth);
            createIcon();
        }

        /// <summary>
        /// Constructs a new IconDeviceImage from a Managed Icon
        /// </summary>
        /// <param name="icon">Icon to construct from</param>
        public IconDeviceImage(
            System.Drawing.Icon icon
            )
        {
            // use DrawIconEx to create bitmaps for the
            // colour and mask images, then use GetDIBits
            // to populate data
        }

        /// <summary>
        /// Constructs a new icon device image from an array of
        /// bytes in the Icon file format
        /// </summary>
        /// <param name="b">Array of bytes</param>
        internal IconDeviceImage(
            byte[] b
            )
        {
            // store the bytes:
            data = new Byte[b.Length];
            for (int i = 0; i < b.Length; i++)
            {
                data[i] = b[i];
            }

            // Read the BitmapInfoHeader structure to get the
            // size and number of bytes:
            BITMAPINFOHEADER bmInfoHeader = new BITMAPINFOHEADER(data);
            //Console.WriteLine(bmInfoHeader.ToString());

            this.size.Width = bmInfoHeader.biWidth;
            this.size.Height = bmInfoHeader.biHeight / 2;
            switch (bmInfoHeader.biBitCount)
            {
                case 1:
                case 4:
                    this.colorDepth = ColorDepth.Depth4Bit;
                    break;

                case 8:
                    this.colorDepth = ColorDepth.Depth8Bit;
                    break;

                case 16:
                    this.colorDepth = ColorDepth.Depth16Bit;
                    break;

                case 24:
                    this.colorDepth = ColorDepth.Depth24Bit;
                    break;

                case 32:
                    this.colorDepth = ColorDepth.Depth32Bit;
                    break;
            }
            createIcon();
        }

        /// <summary>
        /// Clears up any resources associated with this
        /// Icon Device Image.
        /// </summary>
        public void Dispose()
        {
            if (this.hIcon != IntPtr.Zero)
            {
                DestroyIcon(this.hIcon);
                this.hIcon = IntPtr.Zero;
            }
        }

        #endregion Constructor, Dispose
    }

    #endregion IconDeviceImage
}